package se.dolkow.skola.datasakerhet.server.storage;

import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Scanner;
import java.util.logging.Logger;

public class Storage {

	private List<Division> divs = new LinkedList<Division>();
	private List<Doctor> doctors = new LinkedList<Doctor>();
	private List<Nurse> nurses = new LinkedList<Nurse>();
	private List<Agency> agencies = new LinkedList<Agency>();
	private List<Patient> patients = new LinkedList<Patient>();
	
	private Logger log;
	
	private final File f;
	
	public Storage(File f) throws IOException, ParseException {
		this(new FileReader(f), f);
	}

	/**
	 * 
	 * @param is
	 * @param storage if null, don't auto-save storage after changes.
	 * @throws IOException
	 * @throws ParseException
	 */
	public Storage(Readable is, File storage) throws IOException, ParseException {
		log = Logger.getLogger("se.dolkow.skola.datasakerhet.server.storage");
		log.fine("Initializing storage...");
		
		this.f = storage;
		
		Scanner sc = new Scanner(is);
		
		while(sc.hasNext()) {
			String type = sc.next().toLowerCase();
			String line = sc.nextLine().trim();
			Scanner sc2 = new Scanner(line).useDelimiter("\\s*;\\s*");
			if (type.startsWith("div")) {
				divs.add(new Division(sc2.next()));
			} else if (type.startsWith("doc") || type.startsWith("nur")) {
				String name = sc2.next();
				String divName = sc2.next();
				Division div = getDivision(divName);
				if (div == null) {
					throw new ParseException("Couldn't find division " + divName + " for " + type + " " + line, -1);
				}
				if (type.startsWith("doc")) {
					doctors.add(new Doctor(name, div));
				} else { 
					nurses.add(new Nurse(name, div));
				}
			} else if (type.startsWith("pat")) {
				String name = sc2.next();
				String docName = sc2.next();
				Doctor doc = getDoctor(docName);
				if (doc == null) {
					throw new ParseException("Couldn't find doctor " + docName + " for " + type + " " + line, -1);
				}
				patients.add(new Patient(name, doc));
			} else if (type.startsWith("age")) {
				String agencyName = sc2.next();
				agencies.add(new Agency(agencyName));
			} else if (type.startsWith("rec")) {
				String content = sc2.next();
				String patName = sc2.next();
				Patient pat = getPatient(patName);
				if (pat == null) {
					throw new ParseException("Couldn't find patient " + patName + " for " + type + " " + line, -1);
				}
				String docName = sc2.next();
				Doctor doc = getDoctor(docName);
				if (doc == null) {
					throw new ParseException("Couldn't find doctor " + docName + " for " + type + " " + line, -1);
				}
				String nurName = sc2.next();
				Nurse nurse = getNurse(nurName);
				if (nurse == null) {
					throw new ParseException("Couldn't find nurse " + nurName + " for " + type + " " + line, -1);
				}
				String divName = sc2.next();
				Division div = getDivision(divName);
				if (div == null) {
					throw new ParseException("Couldn't find division " + divName + " for " + type + " " + line, -1);
				}
				pat.records.add(new Record(content, pat, doc, nurse, div));
			} else {
				throw new ParseException("Unknown line type \"" + type + "\".", -1);
			}
		}
		log.fine("Storage initialized.");
	}
	
	public void writeTo(PrintWriter w) {
		log.fine("Writing storage...");
		for(Division div : divs) {
			w.print("division ; ");
			w.print(div.getName());
			w.println();
		}
		for(Doctor doc : doctors) {
			w.print("doctor ; ");
			w.print(doc.getName() + "; " + doc.getDivision().getName());
			w.println();
		}
		for(Nurse nurse : nurses) {
			w.print("nurse ; ");
			w.print(nurse.getName() + "; " + nurse.getDivision().getName());
			w.println();
		}
		for(Patient p : patients) {
			w.print("patient ; ");
			w.print(p.getName() + "; " + p.getDoctor().getName());
			w.println();
			for(Record r : p.records) {
				w.print("record ; ");
				w.print(r.getContent() + "; " + 
						r.getPatient().getName() + "; " + 
						r.getDoctor().getName() + "; " + 
						r.getNurse().getName() + "; " + 
						r.getDivision().getName());
				w.println();
			}
		}
		for(Agency a : agencies) {
			w.print("agency ; ");
			w.print(a.getName());
			w.println();
		}
		w.flush();
		log.fine("Storage written.");
	}
	
	public void store(File f) throws IOException {
		FileWriter fw = new FileWriter(f, false);
		writeTo(new PrintWriter(fw));
		fw.flush();
		fw.close();
	}
	
	private void store() throws IOException {
		if (f != null) {
			store(f);
		}
	}
		
	
	public Division getDivision(String name) {
		for(Division div : divs) {
			if (div.getName().equals(name)) {
				return div;
			}
		}
		return null;
	}
	
	public User getUser(String name) {
		User u = getDoctor(name);
		u = u == null ? getNurse(name) : u;
		u = u == null ? getAgency(name) : u;
		u = u == null ? getPatient(name) : u;
		return u;
	}
	
	public Nurse getNurse(String name) {
		for (Nurse n : nurses) {
			if (n.getName().equals(name)) {
				return n;
			}
		}
		return null;
	}
	
	public Agency getAgency(String name) {
		for (Agency a : agencies) {
			if (a.getName().equals(name)) {
				return a;
			}
		}
		return null;
	}
	
	public Doctor getDoctor(String name) {
		for (Doctor d : doctors) {
			if (d.getName().equals(name)) {
				return d;
			}
		}
		return null;
	}
	
	public Patient getPatient(String name) {
		for(Patient p : patients) {
			if (p.getName().equals(name)) {
				return p;
			}
		}
		return null;
	}
	
	/**
	 * @param u
	 * @return all records u can read
	 */
	public ArrayList<Record> getRecords(User u) {
		ArrayList<Record> res = new ArrayList<Record>();
		for(Patient p : patients) {
			for(Record r : p.records) {
				if(u.isAuthorized(Action.READ, r)) {
					res.add(r);
				}
			}
		}
		return res;
	}
	
	/**
	 * 
	 * @param id
	 * @param u
	 * @return the record, if found, and null otherwise.
	 * @throws PermissionDeniedException if the record exists, but u is not 
	 * allowed to read it
	 */
	public Record getRecord(int id, User u) throws PermissionDeniedException {
		for(Patient p : patients) {
			for(Record r : p.records) {
				if (r.getId() == id) {
					assertPermission(u, Action.READ, r);
					return r;
				}
			}
		}
		return null;
	}
	
	/**
	 * @param r
	 * @param u
	 * @return content, if authorized
	 * @throws PermissionDeniedException
	 */
	public String getContent(Record r, User u) throws PermissionDeniedException {
		assertPermission(u, Action.READ, r);
		return r.getContent();
	}
	
	/**
	 * @param r
	 * @param newContent
	 * @param u
	 * @throws PermissionDeniedException
	 * @throws IOException 
	 */
	public void setContent(Record r, String newContent, User u) throws PermissionDeniedException, IOException {
		assertPermission(u, Action.WRITE, r);
		r.setContent(newContent);
		store();
	}
	
	/**
	 * @param p
	 * @param n
	 * @param creator
	 * @throws PermissionDeniedException
	 * @throws IOException 
	 */
	public Record createRecord(Patient p, Nurse n, Doctor d, User creator) throws PermissionDeniedException, IOException {
		if (!creator.canCreateFor(p)) {
			denyPermission(creator, Action.CREATE, null);
		}
		
		if (d == null) {
			d = (Doctor)creator;
		}
		
		Record r = new Record(p, d, n, d.getDivision());
		p.records.add(r);
		grantPermission(creator, Action.CREATE, r);
		
		store();
		return r;
	}
	
	/**
	 * @param r
	 * @param u
	 * @throws PermissionDeniedException
	 * @throws IOException 
	 */
	public boolean deleteRecord(Record r, User u) throws PermissionDeniedException, IOException {
		assertPermission(u, Action.DELETE, r);
		boolean b = r.getPatient().records.remove(r);
		store();
		return b;
	}

	private void assertPermission(User u, Action a, Record r) throws PermissionDeniedException {
		if (!u.isAuthorized(a, r)) {
			denyPermission(u, a, r);
		} else {
			grantPermission(u, a, r);
		}
	}
	
	private void denyPermission(User u, Action a, Record r) throws PermissionDeniedException {
		String logmsg = u + " was denied permission to " + a + " a record (";
		if (r != null) {
			logmsg += "doc: " + r.getDoctor() + ", pat: " + r.getPatient() +
			", nurse: " + r.getNurse() + ", div: " + r.getDivision() + ").";
		} else {
			logmsg += "null)";
		}
		log.warning(logmsg);
		throw new PermissionDeniedException(a + ": permission denied for " + u);
	}
	
	private void grantPermission(User u, Action a, Record r) {
		String logmsg = u + " was allowed permission to " + a + " a record (";
		if (r != null) {
			logmsg += "doc: " + r.getDoctor() + ", pat: " + r.getPatient() +
			", nurse: " + r.getNurse() + ", div: " + r.getDivision() + ").";
		} else {
			logmsg += "null)";
		}
		log.info(logmsg);
	}
}
